package edu.kit.csl.pisa.io;

/*
This file is part of the PISA Alignment Tool.

Copyright (C) 2013
Karlsruhe Institute of Technology
Cognitive Systems Lab (CSL)
Felix Stahlberg

PISA is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

PISA is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with PISA. If not, see <http://www.gnu.org/licenses/>.
*/

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;

import edu.kit.csl.pisa.datatypes.Alignment;
import edu.kit.csl.pisa.datatypes.Corpus;
import edu.kit.csl.pisa.datatypes.SentencePair;
import edu.kit.csl.pisa.ui.Configuration;

/**
 * This class is a concrete factory in the GoF design pattern Abstract Factory.
 * It loads the corpus from GIZA++' output files.
 */
public class GizaCorpusFactory extends CorpusFactory {

	private final String GIZA_ALIGN_FILE_EXTENSION = ".A3.finalNBEST";
	
	private int discarded = 0;
	
	/**
	 * Sole constructor.
	 */
	public GizaCorpusFactory() {
		super();
	}

	/**
	 * Loads the initial Corpus instance from GIZA++' output files. In order to
	 * specify their location, make sure that initialModelPath is set to the same 
	 * value as the -o parameter was at the GIZA++ run.
	 */
	@Override
	public Corpus createCorpus() {
		Corpus corpus = new Corpus();
		discarded = 0;
		try {
			String fileName = Configuration.getSingleton().getString(
					"initialModelPath") + GIZA_ALIGN_FILE_EXTENSION;
			FileInputStream fstream = new FileInputStream(fileName);
			DataInputStream in = new DataInputStream(fstream);
			BufferedReader br = new BufferedReader(new InputStreamReader(in));
			Logger.getSingleton().notice("Load GIZA++ corpus from "
					+ fileName);
			String line;
			List<String> alignStrings = new LinkedList<String>();
			String trgtSentence = null;
			String curSenId = "1";
			int curLineRole = 0; // 0: comment, 1: trgt sen, 2: src sen
			// Read File Line By Line
			while ((line = br.readLine()) != null) {
				switch (curLineRole) {
				case 0: // We are in the "# Sentence pair (..).." line
					Matcher matcher = senIdPattern.matcher(line);
					if (!matcher.find()) {
						throw new Exception("Malformed GIZA++ file. "
								+ "Could not fetch sentence ID from line "
								+ line);
					}
					String newSenId = matcher.group(1);
					if (!newSenId.equals(curSenId)) { // New sentence id
						addToCorpus(corpus, trgtSentence, alignStrings, curSenId);
						alignStrings.clear();
						curSenId = newSenId;
					}
					break;
				case 1: // We are in a source language line
					trgtSentence = line;
					break;
				case 2: // we are in a target language line with the alignment
					alignStrings.add(line);
					break;
				}
				curLineRole = (curLineRole + 1) % 3;
			}
			addToCorpus(corpus, trgtSentence, alignStrings, curSenId); // last
			in.close();
		} catch (Exception e) {// Catch exception if any
			Logger.getSingleton().fatalError("Error while reading GIZA++ "
					+ "alignment file: " + e.toString());
		}
		if (discarded > 0) {
			Logger.getSingleton().warn(discarded + " GIZA++ alignments "
					+ "discarded: Inconsistent with maxFertility, "
					+ "maxWordLength, max[Trgt|Src]SenLen"
					+ "parameters or malformed GIZA++ file.");
		}
		return corpus;
	}

	/* (non-Javadoc)
	 * Add a new sentence pair to the corpus. The sentences are given in their
	 * string representation (as they appear in the GIZA++ alignment file). The
	 * alignment is intrinsicly stored in elements of alignStrings. We assume 
	 * that elements in alignStrings are sorted by the alignment score
	 * (decreasing), so the first element provides the best alignment.
	 * 
	 * @param corpus
	 * @param trgtString
	 * @param alignStrings GIZA++ target sentence string defining the alignment
	 * @param id sentence pair id
	 */
	private void addToCorpus(Corpus corpus, String trgtString,
			List<String> alignStrings, String id) {
		if (alignStrings.size() == 0) {
			Logger.getSingleton().error("No alignments found. "
					+ "Discard sentence pair.");
			return;
		}
		SentencePair dataSet = createSentencePair(trgtString,
				alignStrings.get(0), id);
		// Add alignments
		int score = 0;
		final int m = dataSet.getSourceSentence().getLength();
		final int n = dataSet.getTargetSentence().getLength();
		for (String alignString : alignStrings) {
			Alignment a = createAlignment(alignString, m, n);
			if (a != null && a.isConsistent(m, n)) {
				dataSet.addAlignment(a, score--);
			} else {
				discarded++;
			}
		}
		if (dataSet.countAlignments() > 0) {
			if (!corpus.add(dataSet)) {
				discarded++;
			}
		}
	}

	/*
	 * Creates an Alignment instance out of a GIZA++ alignment string
	 * 
	 * @param alignString
	 * @param srcLen length of the source sentence
	 * @param trgtLen length of the target sentence
	 * @return Alignment if the string could be parsed, null if not
	 */
	private Alignment createAlignment(String alignString,
			int srcLen, int trgtLen) {
		try {
			updateAlignmentArrays(alignString, trgtLen);
			// Create word boundary array
			boolean[] wordBoundaries = new boolean[trgtLen];
			for (int i = 1; i < trgtLen; i++) {
				wordBoundaries[i] = (alignment[i - 1] != alignment[i]);
			}
			return new Alignment(srcLen, alignment, wordBoundaries, locks);
		} catch (ArrayIndexOutOfBoundsException e) {
			return null;
		}
	}
}
